package com.alameda.service.user;

import com.alameda.api.statement.SystemRecordService;
import com.alameda.api.user.UserService;
import com.alameda.base.constant.LoginConstant;
import com.alameda.commonpent.redis.api.RedisCacheTemplate;
import com.alameda.exception.BusinessException;
import com.alameda.mapper.user.UserMapper;
import com.alameda.statement.enums.SystemRecordEnum;
import com.alameda.user.dto.UserDTO;
import com.alameda.user.pojo.UserDO;
import com.alameda.user.vo.UserVO;
import com.alameda.utils.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.code.kaptcha.Producer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.alameda.base.constant.LoginConstant.TOKEN;
import static com.alameda.base.constant.ProjectConstant.CAPTCHA_CODE;

/**
 * 用户 业务逻辑层 具体实现
 *
 * @author Lv
 * @date 2024/2/19
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {
    /**
     * 注册账号
     *
     * @param userDTO 用户数据传递对象
     * @return
     */
    @Override
    public Boolean register(UserDTO userDTO) {
        // 校验数据
        checkData(userDTO);

        // 写入DB
        return this.save(UserDO.builder()
                .username(userDTO.getUsername())
                .password(MdFiveUtils.getMdFiveString(userDTO.getPassword()))
                .nickname(userDTO.getNickname())
                .organizationId(userDTO.getOrganizationId())
                .email(userDTO.getEmail())
                .build());
    }

    @Autowired
    private RedisCacheTemplate redisCacheTemplate;

    /**
     * 是否需要验证码
     */
    @Value("${login-code}")
    private String needCode;

    @Autowired
    private SystemRecordService systemRecordService;

    /**
     * 登录系统
     *
     * @param userDTO 用户数据传递对象
     * @return
     */
    @Override
    public Map<String, Object> login(UserDTO userDTO) {
        UserDO userDO = findByUserName(userDTO.getUsername());
        if (PubUtils.isNull(userDO)) {
            BusinessException.throwException("账号错误");
        }

        if (!userDO.getPassword().equals(MdFiveUtils.getMdFiveString(userDTO.getPassword()))) {
            BusinessException.throwException("密码错误");
        }

        if (Boolean.parseBoolean(needCode)) {
            checkCode(userDTO);
        }

        //登录成功 生成token
        Map<String, Object> claimMap = new HashMap<>(16);
        claimMap.put(LoginConstant.ID, userDO.getId());
        claimMap.put(LoginConstant.USERNAME, userDO.getUsername());
        String token = JwtUtils.genToken(claimMap);

        // token 写入redis 一个小时过期
        redisCacheTemplate.setCacheObject(token, token, 1, TimeUnit.HOURS);
        Map<String, Object> map = MapUtils.newHashMap();
        map.put(TOKEN, token);
        // 存储token
        saveOrUpdateTokenInfo(userDO, token);
        // 存储系统信息记录
        systemRecordService.saveRecord(SystemRecordEnum.LOGIN);
        return map;
    }

    /**
     * token存储到扩展字段
     *
     * @param userDO 用户信息
     * @param token  登录令牌
     * @return
     */
    private Boolean saveOrUpdateTokenInfo(UserDO userDO, String token) {
        Map<String, String> extDataMap = userDO.getExtData();
        extDataMap = Optional.ofNullable(extDataMap).orElse(MapUtils.newHashMap());
        extDataMap.put(TOKEN, token);
        userDO.setExtData(extDataMap);
        return this.saveOrUpdate(userDO);
    }

    /**
     * 校验验证码
     *
     * @param userDTO 用户数据传递对象
     */
    private void checkCode(UserDTO userDTO) {
        String cashCode = redisCacheTemplate.getCacheObject(CAPTCHA_CODE + userDTO.getUuid());
        if (PubUtils.isEmpty(cashCode) || !userDTO.getCode().equals(cashCode)) {
            BusinessException.throwException("验证码错误");
        }
    }

    /**
     * 获取当前登录用户信息
     *
     * @return
     */
    @Override
    public UserVO getUserInfo() {
        String userName = ThreadLocalUtils.getUserName();
        UserDO userDO = findByUserName(userName);

        UserVO userVO = ObjectUtils.convert(userDO, UserVO.class);
        userVO.setName("Super Admin");
        userVO.setAvatar("https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
        userVO.setRoles("admin");
        userVO.setIntroduction("I am a super administrator");
        return userVO;
    }

    /**
     * 修改用户信息
     *
     * @param userDTO 用户数据传递对象
     * @return
     */
    @Override
    public Boolean edit(UserDTO userDTO) {
        UserDO userDO = ObjectUtils.convert(userDTO, UserDO.class);
        return this.updateById(userDO);
    }

    @Autowired
    private Producer captchaProducerMath;

    /**
     * 生成验证码
     *
     * @return
     */
    @Override
    public Map<String, String> getImageCode() {
        // 生成UUID作为key
        String uuid = UUID.randomUUID().toString();

        // 生成验证码
        String capText = captchaProducerMath.createText();
        String capStr = capText.substring(0, capText.lastIndexOf("@"));
        String code = capText.substring(capText.lastIndexOf("@") + 1);
        BufferedImage image = captchaProducerMath.createImage(capStr);

        // 定义redis存储key
        String verifyKey = CAPTCHA_CODE + uuid;
        log.info("redis存储key :{}", verifyKey);
        redisCacheTemplate.setCacheObject(verifyKey, code, 20, TimeUnit.MINUTES);

        // 转换流信息写出
        FastByteArrayOutputStream fastByteArrayOutputStream = new FastByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", fastByteArrayOutputStream);
        } catch (IOException e) {
            BusinessException.throwException("验证码生成错误");
        }

        Map<String, String> map = new HashMap<>(16);
        map.put("uuid", uuid);
        map.put("img", "data:image/gif;base64," + Base64Utils.encode(fastByteArrayOutputStream.toByteArray()));
        return map;
    }

    /**
     * 退出登录
     *
     * @return
     */
    @Override
    public Boolean logout() {
        String token = ThreadLocalUtils.getToken();
        UserDO userDO = findByToken(token);
        // 抹掉token记录
        saveOrUpdateTokenInfo(userDO, null);
        return redisCacheTemplate.deleteObject(token);
    }

    /**
     * 查询用户列表
     *
     * @param userDTO 用户数据传递对象
     * @return
     */
    @Override
    public IPage<UserVO> loadUser(UserDTO userDTO) {
        log.info("通过子类方法com.alameda.service.user.UserServiceImplExt.loadUser实现");
        return null;
    }

    /**
     * 导出excel
     *
     * @param userDTO 用户数据传递对象
     */
    @Override
    public void export(UserDTO userDTO) {
        log.info("通过子类方法com.alameda.service.user.UserServiceImplExt.export实现");
    }

    /**
     * 删除组织下的用户
     *
     * @param organizationIdList 组织id集合
     * @return
     */
    @Override
    public Boolean removeByOrganizationIdList(List<Long> organizationIdList) {
        log.info("通过子类方法com.alameda.service.user.UserServiceImplExt.removeByOrganizationIdList实现");
        return null;
    }

    @Override
    public UserDO findByToken(String token) {
        LambdaQueryWrapper<UserDO> lambdaQueryWrapper = Wrappers.lambdaQuery(UserDO.class).apply("ext_data ->> '$.token' = {0}", token);
        return this.getOne(lambdaQueryWrapper);
    }

    /**
     * 处理登录续期
     *
     * @param userDO 用户信息
     * @return
     */
    @Override
    public Boolean dealRenewal(UserDO userDO) {
        Map<String, String> extDataMap = userDO.getExtData();
        String token = extDataMap.get(TOKEN);
        redisCacheTemplate.setCacheObject(token, token, 1, TimeUnit.HOURS);
        return Boolean.TRUE;
    }

    /**
     * 最小长度
     */
    private static final Short MIN_LENGTH = 5;

    /**
     * 最大长度
     */
    private static final Short MAX_LENGTH = 16;

    /**
     * 校验数据
     *
     * @param userDTO 用户数据传递对象
     */
    private void checkData(UserDTO userDTO) {
        String username = userDTO.getUsername();
        if (PubUtils.isEmpty(username) || username.length() < MIN_LENGTH || username.length() > MAX_LENGTH) {
            BusinessException.throwException("注册账号有误,长度不符");
        }

        UserDO userDO = findByUserName(username);
        if (PubUtils.isNotNull(userDO)) {
            BusinessException.throwException("账号已注册");
        }

        String password = userDTO.getPassword();
        if (PubUtils.isEmpty(password) || password.length() < MIN_LENGTH || password.length() > MAX_LENGTH) {
            BusinessException.throwException("注册密码有误");
        }
    }

    /**
     * 通过账号查询
     *
     * @param username 账号
     * @return
     */
    private UserDO findByUserName(String username) {
        return this.findOne(UserDO.builder().username(username).build());
    }
}
